<template>
	<span class="at-cascader" :class="[
		        {
		          'is-opened': menuVisible,
		          'is-disabled': disabled
		        },
		        size ? 'at-cascader--' + size : ''
		      ]" @click="handleClick" @mouseenter="inputHover = true" @mouseleave="inputHover = false" ref="reference" v-clickoutside="handleClickoutside">
		<at-input ref="input" :readonly="!filterable" :placeholder="currentLabels.length ? undefined : placeholder" v-model="inputValue" @change="debouncedInputChange" :validate-event="false" :size="size" :disabled="disabled">
			<template slot="icon">
				<i key="1" v-if="clearable && inputHover && currentLabels.length" class="at-input__icon at-icon-circle-close at-cascader__clearIcon" @click="clearValue"></i>
				<i key="2" v-else class="at-input__icon at-icon-caret-bottom" :class="{ 'is-reverse': menuVisible }"></i>
			</template>
		</at-input>
		<span class="at-cascader__label" v-show="inputValue === ''">
			<template v-if="showAllLevels">
				<template v-for="(label, index) in currentLabels">
					{{ label }}
					<span v-if="index < currentLabels.length - 1"> / </span>
				</template>
			</template>
			<template v-else>
				{{ currentLabels[currentLabels.length - 1] }}
			</template>
		</span>
	</span>
</template>

<script>
import Vue from 'vue';
import ATCascaderMenu from './menu';
// import input from '../../Input/input.js';
import Popper from '../../common/utils/vue-popper';
import Clickoutside from '../../common/utils/clickoutside';
import emitter from '../../common/mixins/emitter';
import Locale from '../../common/mixins/locale';
import { t } from '../../common/locale';
import debounce from 'throttle-debounce/debounce';

const popperMixin = {
	props: {
		placement: {
			type: String,
			default: 'bottom-start'
		},
		appendToBody: Popper.props.appendToBody,
		offset: Popper.props.offset,
		boundariesPadding: Popper.props.boundariesPadding,
		popperOptions: Popper.props.popperOptions
	},
	methods: Popper.methods,
	data: Popper.data,
	beforeDestroy: Popper.beforeDestroy
};

export default {
	name: 'at-cascader',

	directives: { Clickoutside },

	mixins: [popperMixin, emitter, Locale],

	// components: {
	// 	input
	// },

	props: {
		options: {
			type: Array,
			required: true
		},
		props: {
			type: Object,
			default() {
				return {
					children: 'children',
					label: 'label',
					value: 'value',
					disabled: 'disabled'
				};
			}
		},
		value: {
			type: Array,
			default() {
				return [];
			}
		},
		placeholder: {
			type: String,
			default() {
				return t('at.cascader.placeholder');
			}
		},
		disabled: Boolean,
		clearable: {
			type: Boolean,
			default: false
		},
		changeOnSelect: Boolean,
		popperClass: String,
		expandTrigger: {
			type: String,
			default: 'click'
		},
		filterable: Boolean,
		size: String,
		showAllLevels: {
			type: Boolean,
			default: true
		},
		debounce: {
			type: Number,
			default: 300
		},
		beforeFilter: {
			type: Function,
			default: () => (() => { })
		}
	},

	data() {
		return {
			currentValue: this.value || [],
			menu: null,
			debouncedInputChange() { },
			menuVisible: false,
			inputHover: false,
			inputValue: '',
			flatOptions: null
		};
	},

	computed: {
		labelKey() {
			return this.props.label || 'label';
		},
		valueKey() {
			return this.props.value || 'value';
		},
		childrenKey() {
			return this.props.children || 'children';
		},
		currentLabels() {
			let options = this.options;
			let labels = [];
			this.currentValue.forEach(value => {
				const targetOption = options && options.filter(option => option[this.valueKey] === value)[0];
				if (targetOption) {
					labels.push(targetOption[this.labelKey]);
					options = targetOption[this.childrenKey];
				}
			});
			return labels;
		}
	},

	watch: {
		menuVisible(value) {
			value ? this.showMenu() : this.hideMenu();
		},
		value(value) {
			this.currentValue = value;
		},
		currentValue(value) {
			this.dispatch('AtFormItem', 'at.form.change', [value]);
		},
		options: {
			deep: true,
			handler(value) {
				if (!this.menu) {
					this.initMenu();
				}
				this.flatOptions = this.flattenOptions(this.options);
				this.menu.options = value;
			}
		}
	},

	methods: {
		initMenu() {
			this.menu = new Vue(ATCascaderMenu).$mount();
			this.menu.options = this.options;
			this.menu.props = this.props;
			this.menu.expandTrigger = this.expandTrigger;
			this.menu.changeOnSelect = this.changeOnSelect;
			this.menu.popperClass = this.popperClass;
			this.popperElm = this.menu.$el;
			this.menu.$on('pick', this.handlePick);
			this.menu.$on('activeItemChange', this.handleActiveItemChange);
			this.menu.$on('menuLeave', this.doDestroy);
		},
		showMenu() {
			if (!this.menu) {
				this.initMenu();
			}

			this.menu.value = this.currentValue.slice(0);
			this.menu.visible = true;
			this.menu.options = this.options;
			this.$nextTick(_ => {
				this.updatePopper();
				this.menu.inputWidth = this.$refs.input.$el.offsetWidth - 2;
			});
		},
		hideMenu() {
			this.inputValue = '';
			this.menu.visible = false;
		},
		handleActiveItemChange(value) {
			this.$nextTick(_ => {
				this.updatePopper();
			});
			this.$emit('active-item-change', value);
		},
		handlePick(value, close = true) {
			this.currentValue = value;
			this.$emit('input', value);
			this.$emit('change', value);

			if (close) {
				this.menuVisible = false;
			} else {
				this.$nextTick(this.updatePopper);
			}
		},
		handleInputChange(value) {
			if (!this.menuVisible) return;
			const flatOptions = this.flatOptions;

			if (!value) {
				this.menu.options = this.options;
				this.$nextTick(this.updatePopper);
				return;
			}

			let filteredFlatOptions = flatOptions.filter(optionsStack => {
				return optionsStack.some(option => new RegExp(value, 'i').test(option[this.labelKey]));
			});

			if (filteredFlatOptions.length > 0) {
				filteredFlatOptions = filteredFlatOptions.map(optionStack => {
					return {
						__IS__FLAT__OPTIONS: true,
						value: optionStack.map(item => item[this.valueKey]),
						label: this.renderFilteredOptionLabel(value, optionStack)
					};
				});
			} else {
				filteredFlatOptions = [{
					__IS__FLAT__OPTIONS: true,
					label: this.t('at.cascader.noMatch'),
					value: '',
					disabled: true
				}];
			}
			this.menu.options = filteredFlatOptions;
			this.$nextTick(this.updatePopper);
		},
		renderFilteredOptionLabel(inputValue, optionsStack) {
			return optionsStack.map((option, index) => {
				const label = option[this.labelKey];
				const keywordIndex = label.toLowerCase().indexOf(inputValue.toLowerCase());
				const labelPart = label.slice(keywordIndex, inputValue.length + keywordIndex);
				const node = keywordIndex > -1 ? this.highlightKeyword(label, labelPart) : label;
				return index === 0 ? node : [' / ', node];
			});
		},
		highlightKeyword(label, keyword) {
			const h = this._c;
			return label.split(keyword)
				.map((node, index) => index === 0 ? node : [
					h('span', { class: { 'at-cascader-menu__item__keyword': true } }, [this._v(keyword)]),
					node
				]);
		},
		flattenOptions(options, ancestor = []) {
			let flatOptions = [];
			options.forEach((option) => {
				const optionsStack = ancestor.concat(option);
				if (!option[this.childrenKey]) {
					flatOptions.push(optionsStack);
				} else {
					if (this.changeOnSelect) {
						flatOptions.push(optionsStack);
					}
					flatOptions = flatOptions.concat(this.flattenOptions(option[this.childrenKey], optionsStack));
				}
			});
			return flatOptions;
		},
		clearValue(ev) {
			ev.stopPropagation();
			this.handlePick([], true);
		},
		handleClickoutside() {
			this.menuVisible = false;
		},
		handleClick() {
			if (this.disabled) return;
			if (this.filterable) {
				this.menuVisible = true;
				this.$refs.input.$refs.input.focus();
				return;
			}
			this.menuVisible = !this.menuVisible;
		}
	},

	created() {
		this.debouncedInputChange = debounce(this.debounce, value => {
			const before = this.beforeFilter(value);

			if (before && before.then) {
				this.menu.options = [{
					__IS__FLAT__OPTIONS: true,
					label: this.t('at.cascader.loading'),
					value: '',
					disabled: true
				}];
				before
					.then(() => {
						this.$nextTick(() => {
							this.handleInputChange(value);
						});
					});
			} else if (before !== false) {
				this.$nextTick(() => {
					this.handleInputChange(value);
				});
			}
		});
	},

	mounted() {
		this.flatOptions = this.flattenOptions(this.options);
	}
};
</script>
